const fs = require('fs');
const path = require('path');
const { chromium } = require('playwright');
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });

const USER_ID = process.env.LUOGU_USER_ID || '322081';
const START_PAGE = parseInt(process.env.START_PAGE || '1', 10);
const END_PAGE = parseInt(process.env.END_PAGE || START_PAGE, 10);
const COOKIE = process.env.LUOGU_COOKIE || '';
const OUTPUT_DIR = process.env.OUTPUT_DIR || path.join(process.cwd(), 'output');
const STATE_PATH = path.join(OUTPUT_DIR, 'state.json');

const BASE = 'https://www.luogu.com.cn';

function ensureDir(dir) {
  fs.mkdirSync(dir, { recursive: true });
}

function sanitize(text) {
  return (text || '').replace(/[\n\r]+/g, ' ').replace(/\s+/g, ' ').trim();
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

async function withRetry(fn, { retries = 3, delay = 500 } = {}) {
  let lastErr;
  for (let i = 0; i < retries; i++) {
    try { return await fn(); } catch (e) { lastErr = e; await sleep(delay); }
  }
  throw lastErr;
}

async function collectRecordLinks(page, pageNo) {
  const url = `${BASE}/record/list?user=${USER_ID}&page=${pageNo}`;
  await page.goto(url, { waitUntil: 'networkidle' });
  // 等待列表渲染（尽量稳健）
  await page.waitForTimeout(800);
  const links = await page.evaluate(() => {
    const as = Array.from(document.querySelectorAll('a'));
    const hrefs = as.map(a => a.href).filter(h => h.includes('/record/') && !h.includes('/record/list'));
    return Array.from(new Set(hrefs));
  });
  return links;
}

async function extractRecordDetail(page, url) {
  await page.goto(url, { waitUntil: 'networkidle' });
  await page.waitForTimeout(800);
  const data = await page.evaluate(() => {
    const textFrom = (sel) => {
      const el = document.querySelector(sel);
      return el ? el.textContent : '';
    };
    // 兜底：在页面中找可能的字段
    const titleEl = document.querySelector('[class*="title"], h1, .lg-title');
    const title = titleEl ? titleEl.textContent : '';
    const metaText = Array.from(document.querySelectorAll('div, span, p')).map(e => e.textContent).join('\n');
    // 代码块常见标签
    const codeEl = document.querySelector('pre code') || document.querySelector('pre') || document.querySelector('code');
    const code = codeEl ? codeEl.textContent : '';
    // 可能出现的题号模式如 Pxxxx 或 CFxxxx
    const problemMatch = metaText.match(/\b([A-Z]{1,3}\d{1,6})\b/);
    const problemId = problemMatch ? problemMatch[1] : '';
    // 记录 ID（从 URL 或页面文本中）
    const ridMatch = location.pathname.match(/record\/(\d+)/);
    const rid = ridMatch ? ridMatch[1] : '';
    // 语言
    const langMatch = metaText.match(/语言[:：]\s*([A-Za-z0-9\+\#]+)/);
    const lang = langMatch ? langMatch[1] : '';
    // 提交时间
    const timeMatch = metaText.match(/提交时间[:：]\s*([^\n]+)/);
    const submitTime = timeMatch ? timeMatch[1].trim() : '';
    return { title, code, problemId, rid, lang, submitTime };
  });
  data.title = sanitize(data.title);
  data.code = data.code || '';
  // 如果代码未加载，尝试点击显示按钮（若存在）
  if (!data.code) {
    try {
      const btn = await page.$('text=查看代码, text=显示代码');
      if (btn) { await btn.click(); await page.waitForTimeout(500); }
      const codeEl = await page.$('pre code, pre, code');
      if (codeEl) data.code = await codeEl.textContent();
    } catch (e) {}
  }
  // 如果仍然没有代码，标记为需截图原页
  return data;
}

async function renderTemplateAndScreenshot(page, info, outPath) {
  const templatePath = path.join(__dirname, 'template.html');
  const params = new URLSearchParams({
    problem: info.problemId || '',
    title: info.title || '',
    rid: info.rid || '',
    lang: info.lang || '',
    time: info.submitTime || '',
    code: info.code || ''
  });
  const url = 'file://' + templatePath.replace(/\\/g, '/') + '?' + params.toString();
  await page.goto(url);
  const el = await page.$('#canvas');
  await el.screenshot({ path: outPath, animations: 'disabled' });
}

function readState() {
  try { return JSON.parse(fs.readFileSync(STATE_PATH, 'utf-8')); } catch { return { done: {} }; }
}

function writeState(state) {
  try { fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2)); } catch {}
}

async function buildGalleryHtml() {
  const files = fs.readdirSync(OUTPUT_DIR).filter(f => f.endsWith('.png')).sort();
  const items = files.map(f => `<div class="item"><img src="${f}" /><div class="name">${f}</div></div>`).join('\n');
  const html = `<!doctype html><html><head><meta charset="utf-8"/><style>body{font-family:Consolas,monospace} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;padding:16px} .item img{width:100%;height:auto;border:1px solid #ddd;border-radius:6px} .name{font-size:12px;color:#666;margin-top:4px}</style></head><body><div class="grid">${items}</div></body></html>`;
  fs.writeFileSync(path.join(OUTPUT_DIR, 'index.html'), html);
}

async function main() {
  ensureDir(OUTPUT_DIR);
  const state = readState();
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext();
  const page = await context.newPage();
  if (COOKIE) {
    await page.setExtraHTTPHeaders({ Cookie: COOKIE, 'User-Agent': 'Mozilla/5.0' });
  }
  // 速率与并发（简单串行，后续可并发）
  for (let p = START_PAGE; p <= END_PAGE; p++) {
    console.log(`Page ${p}: collecting records...`);
    const links = await collectRecordLinks(page, p);
    console.log(`Found ${links.length} records on page ${p}`);
    const CHUNK = 4;
    for (let i = 0; i < links.length; i += CHUNK) {
      const batch = links.slice(i, i + CHUNK);
      await Promise.all(batch.map(async (link) => {
        try {
          const info = await withRetry(() => extractRecordDetail(page, link));
          const stamp = (info.submitTime || '').replace(/[^0-9A-Za-z_-]/g, '').slice(0, 20);
          const nameParts = [info.rid || 'rid', info.problemId || 'unknown', stamp || ''];
          const file = nameParts.filter(Boolean).join('_') + '.png';
          const outPath = path.join(OUTPUT_DIR, file);
          const MAX_LEN = 20000;
          if ((info.code || '').length > MAX_LEN) {
            let idx = 0; let part = 1;
            while (idx < info.code.length) {
              const chunk = info.code.slice(idx, idx + MAX_LEN);
              const chunkInfo = { ...info, code: chunk };
              const partOut = outPath.replace(/\.png$/, `_part${part}.png`);
              await renderTemplateAndScreenshot(page, chunkInfo, partOut);
              idx += MAX_LEN; part += 1;
              await sleep(400 + Math.random()*400);
            }
          } else if (info.code) {
            await renderTemplateAndScreenshot(page, info, outPath);
          } else {
            await page.goto(link, { waitUntil: 'networkidle' });
            const clipEl = await page.$('main, body');
            const shotPath = outPath.replace(/\.png$/, `_raw.png`);
            if (clipEl) await clipEl.screenshot({ path: shotPath });
          }
          console.log('Saved', outPath);
          state.done[info.rid || link] = true;
          writeState(state);
          await sleep(400 + Math.random()*400);
        } catch (e) {
          console.warn('Failed for', link, e.message);
          await sleep(800);
        }
      }));
    }
  }
  await buildGalleryHtml();
  await browser.close();
}

main().catch(err => { console.error(err); process.exit(1); });

